#include "ScreenMgr.h"

#include <AssertUtils.h>

const sf::Time ScreenMgr::kMinLoadingScreenTime = sf::seconds(1.0f);

/* ScreenMgr():
 *	    Constructor.
 */
ScreenMgr::ScreenMgr()
: m_activeScreen(nullptr)
, m_loadingScreen(nullptr)
, m_renderWindow(nullptr)
{
}

ScreenMgr* ScreenMgr::instance()
{
	static ScreenMgr instance;
	return &instance;
}

/* draw():
 *	    Draws the active screen.
 */
void ScreenMgr::draw()
{
	Utils::debugAssert(m_activeScreen != nullptr, "Trying to draw an invalid screen object.");
	Utils::debugAssert(m_renderWindow != nullptr, "Trying to draw to an invalid RenderWindow.");

	// Clear the render window, draw to it, display it.
	m_renderWindow->clear();

	// draw backwards so that the screens are drawn in the right order
	for (auto it = m_screenStack.rbegin(); it != m_screenStack.rend(); ++it)
	{
		(*it).first->draw(m_renderWindow);
		(*it).first->drawButtons(m_renderWindow);
	}

	m_renderWindow->display();
}

/* getScreenWidth():
 *		Returns the width of the screen.
 */
int ScreenMgr::getScreenWidth()
{
	return m_renderWindow->getSize().x;
}

/* getScreenHeight():
 *		Returns the height of the screen.
 */
int ScreenMgr::getScreenHeight()
{
	return m_renderWindow->getSize().y;
}

/* getScreenCenter():
 *		Returns the point at the center of the screen.
 */
Vector2D ScreenMgr::getScreenCenter()
{
	return Vector2D((float)m_renderWindow->getSize().x / 2, (float) m_renderWindow->getSize().y / 2);
}

/* setWindow():
 *		 Sets the RenderWindow to draw to.
 */
void ScreenMgr::setWindow(sf::RenderWindow* win)
{
	m_renderWindow = win;
}

/* setLoadingScreen():
 *		Sets the loading screen to use game-wide.
 */
void ScreenMgr::setLoadingScreen(Screen* screen)
{
	Utils::debugAssert(screen != nullptr, "Invalid screen pointer passed to ScreenMgr::setLoadingScreen().");
	m_loadingScreen = screen;
}

/* update():
 *	    Updates the active screen.
 */
void ScreenMgr::update(float dt)
{
	Utils::debugAssert(m_activeScreen != nullptr && m_screenStack.size() > 0, "Trying to update an invalid screen object.");

	std::list<std::pair<Screen*,bool>> screenStackCopy = m_screenStack;
	for (auto it = screenStackCopy.begin(); it != screenStackCopy.end(); ++it)
	{
		// only update if keepUpdating is true
		if ((*it).second)
		{
			(*it).first->update(dt);
		}
	}

	while (!m_deadScreens.empty())
	{
		delete m_deadScreens.front();
		m_deadScreens.pop_front();
	}
}

/* replaceScreen():
 *	    Replaces the active screen.
 */
void ScreenMgr::replaceScreen(Screen* newScreen)
{
	if (m_activeScreen == nullptr)
	{
		newScreen->loadResources();
		m_activeScreen = newScreen;
		m_screenStack.push_front(std::make_pair(m_activeScreen, true));
		return;
	}

	// delete all the old screens before we do anything (memory management).
	while (!m_screenStack.empty())
	{
		m_deadScreens.push_back(m_screenStack.front().first);
		m_screenStack.pop_front();
	}

	if (newScreen->showLoadingScreen())
	{
		Utils::debugAssert(m_loadingScreen != nullptr, "Trying to transition between screens "
            "with a loading screen, invalid loading screen object setup.");
		
		// Replace the active screen with the loading screen and load it's resources,
		// draw and display it, then sleep for a very short period so that the user sees it.
		// May display longer depending on the call to newScreen->loadResources().
		m_activeScreen = m_loadingScreen;
		m_loadingScreen->loadResources();
		
		m_renderWindow->clear();
		m_loadingScreen->draw(m_renderWindow);
		m_renderWindow->display();

		sf::sleep(kMinLoadingScreenTime);
	}
	
	// Load the resources for the new screen
	// and set it.
	newScreen->loadResources();
	m_activeScreen = newScreen;
	m_screenStack.push_front(std::make_pair(m_activeScreen, true));
}

/* pushScreen():
 *		Pushes a new screen onto the stack.
 */
void ScreenMgr::pushScreen(Screen* newScreen, bool keepUpdating)
{
	Utils::debugAssert(m_activeScreen != nullptr, "Must have an existing screen before calling pushScreen!");

	m_activeScreen = newScreen;

	// set the screen below the new one to the keepUpdating value
	// and push the new screen on top with keepUpdating to true
	// (so that the top screen gets update calls.)
	m_screenStack.front().second = keepUpdating;
	m_screenStack.push_front(std::make_pair(newScreen, true));

	if (newScreen->showLoadingScreen())
	{
		Utils::debugAssert(m_loadingScreen != nullptr, "Trying to transition between screens "
            "with a loading screen, invalid loading screen object setup.");
		
		// Replace the active screen with the loading screen and load it's resources,
		// draw and display it, then sleep for a very short period so that the user sees it.
		// May display longer depending on the call to newScreen->loadResources().
		m_activeScreen = m_loadingScreen;
		m_loadingScreen->loadResources();
		
		m_loadingScreen->draw(m_renderWindow);
		m_renderWindow->display();

		sf::sleep(kMinLoadingScreenTime);
	}
	
	// Load the resources for the new screen
	// and set it.
	newScreen->loadResources();
	m_activeScreen = m_screenStack.front().first;
}

/* popScreen():
 *		Pops the top-most screen on the stack.
 */
void ScreenMgr::popScreen()
{
	Utils::debugAssert(m_screenStack.size() > 1, "Attempting to pop the last screen off the stack.");
	
	m_deadScreens.push_back(m_screenStack.front().first);
	m_screenStack.pop_front();
	m_screenStack.front().second = true; // just in case it wasn't set to true on pushScreen();
	m_activeScreen = m_screenStack.front().first;
}

/* keyPressed():
 *	    Called when a key is pressed and the screen is foremost.
 */
void ScreenMgr::keyPressed(sf::Keyboard::Key key)
{
	Utils::debugAssert(m_activeScreen != nullptr, "Trying to process a key press event on an invalid screen object.");
	
	// loop through all of the screens, and if any screen is active and claims the
	// event (returns true on the function called) return from this function.
	for (auto it = m_screenStack.begin(); it != m_screenStack.end(); ++it)
	{
		if ((*it).second && (*it).first->keyPressed(key))
		{
			return;
		}
	}
}

/* mouseUp():
 *	    Called when a mouse button is released within the screen.
 */
void ScreenMgr::mouseUp(sf::Mouse::Button button, sf::Vector2i mousePosition)
{
	Utils::debugAssert(m_activeScreen != nullptr, "Trying to process a mouse event on an invalid screen object.");
	
	// loop through all of the screens, and if any screen is active and claims the
	// event (returns true on the function called) return from this function.
	for (auto it = m_screenStack.begin(); it != m_screenStack.end(); ++it)
	{
		if ((*it).second && (*it).first->mouseUp(button, mousePosition))
		{
			return;
		}
	}
}

/* mouseDown():
 *	    Called when a mouse button is pressed within the screen.
 */
void ScreenMgr::mouseDown(sf::Mouse::Button button, sf::Vector2i mousePosition)
{
	Utils::debugAssert(m_activeScreen != nullptr, "Trying to process a mouse event on an invalid screen object.");
	
	// loop through all of the screens, and if any screen is active and claims the
	// event (returns true on the function called) return from this function.
	for (auto it = m_screenStack.begin(); it != m_screenStack.end(); ++it)
	{
		if ((*it).second && (*it).first->mouseDown(button, mousePosition))
		{
			return;
		}
	}
}

/* mouseMoved():
 *		Called when the mouse is moved by the user.
 */
void ScreenMgr::mouseMoved(sf::Vector2i newPosition)
{
	Utils::debugAssert(m_activeScreen != nullptr, "Trying to process a mouse event on an invalid screen object.");
	sf::Vector2i oldPosition = m_mousePosition;
	m_mousePosition = newPosition;

	// loop through all of the screens, and if any screen is active and claims the
	// event (returns true on the function called) return from this function.
	for (auto it = m_screenStack.begin(); it != m_screenStack.end(); ++it)
	{
		if ((*it).second && (*it).first->mouseMoved(oldPosition, m_mousePosition))
		{
			return;
		}
	}
}

/* ~ScreenMgr():
 *	    Destructor.
 */
ScreenMgr::~ScreenMgr()
{
	// clean up the screen stack
	while (!m_screenStack.empty())
	{
		delete m_screenStack.front().first;
		m_screenStack.pop_front();
	}

	// clean up the loading screen (if set)
	if (m_loadingScreen != nullptr)
	{
		delete m_loadingScreen;
	}
}